package util;

import java.text.Collator;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;

import com.restfb.Connection;
import com.restfb.Parameter;
import com.restfb.json.JsonObject;
import com.restfb.types.NamedFacebookType;

import domain.FacebookProfile;
import domain.FacebookUser;
import domain.Profile;
import domain.User;

public class FacebookMatchHelper implements MatchHelper {
	private volatile static FacebookMatchHelper facebookMatchHelper = new FacebookMatchHelper();
	
	private final String itbaName = "Instituto Tecnologico de Buenos Aires";
	private final String itbaName2 = "ITBA";
	private Map<User, List<FacebookUser>> conflicts = new HashMap<User, List<FacebookUser>>();
	private final List<Profile> matchedProfiles = new ArrayList<Profile>();
	private final List<Profile> conflictedProfiles = new ArrayList<Profile>();
	private final List<Profile> unmatchedProfiles = new ArrayList<Profile>();
	
	public FacebookMatchHelper facebookMatchHelper() {
		return facebookMatchHelper;
	}
	
	/* (non-Javadoc)
	 * @see util.MatchHelper#getConflicts()
	 */
	public Map<User, List<FacebookUser>> getConflicts() {
		return conflicts;
	}

	/* (non-Javadoc)
	 * @see util.MatchHelper#getMatchedProfiles()
	 */
	@Override
	public List<Profile> getMatchedProfiles() {
		return matchedProfiles;
	}

	/* (non-Javadoc)
	 * @see util.MatchHelper#getConflictedProfiles()
	 */
	@Override
	public List<Profile> getConflictedProfiles() {
		return conflictedProfiles;
	}

	/* (non-Javadoc)
	 * @see util.MatchHelper#getUnmatchedProfiles()
	 */
	@Override
	public List<Profile> getUnmatchedProfiles() {
		return unmatchedProfiles;
	}
		
	/* (non-Javadoc)
	 * @see util.MatchHelper#match(com.google.code.linkedinapi.client.LinkedInApiClient, java.util.List)
	 */
	@Override
	public void match(ApiClientBuilder client, List<User> users) {
		TreeMap <Integer, List<FacebookUser>> ranking;
		int limit = 500;
		int i = 0;
		boolean batchFinalized = false;
		boolean tryMatchingIfError = false;
		if (users != null) {
			conflicts.clear();
			matchedProfiles.clear();
			unmatchedProfiles.clear();
			conflictedProfiles.clear();
			for (int j=0; j<users.size(); j++) {
				User user = users.get(j);
				try {
					// If a facebook profile already exists for that user, it updates it, otherwise
					// gets all candidates from facebook, and process them
					if (((user.getProfile().getFacebookProfile() == null || user.getProfile().getFacebookProfile().getName() == null) &&
							!user.getProfile().getFacebookProfile().isBatchUpdated()) || tryMatchingIfError) {
						// Returns a map with all matched facebook profiles with corresponding
						// match "score" which indicates best candidate to import. There may be cases
						// where two candidates has same score, or no candidate is found.
						ranking = matchSingle(client, user);
						if (!(ranking.isEmpty() || hasConflicts(ranking))) {
							// Candidate imported.
							importCandidate(ranking, user, matchedProfiles);
						} else if (!ranking.isEmpty()){
							// Temporary stores for each user, the list of matched profiles
							conflicts.put(user, ranking.lastEntry().getValue());
							conflictedProfiles.add(user.getProfile());
						} else {
							unmatchedProfiles.add(user.getProfile());
						}
					} else {
						updateMatch(client, user, String.valueOf(user.getProfile().getFacebookProfile().getUid()));
					}
					i++;
					if (i >= limit) {
						break;
					}
					batchFinalized = users.indexOf(user) == users.size() -1;
					tryMatchingIfError = false;
				} catch (Exception e) {
					if (!tryMatchingIfError) {
						tryMatchingIfError = true;
						j--;
					}
				}
			}
		}
		if (batchFinalized) {
			for (User user : users) {
				if (user.getProfile() != null && user.getProfile().getFacebookProfile() != null) {
					user.getProfile().getFacebookProfile().setBatchUpdated(false);
				}
			}
		}
	}
	
	/* (non-Javadoc)
	 * @see util.MatchHelper#importCandidate(domain.User, java.lang.String)
	 */
	@Override
	public boolean resolveCandidate(User user, String facebookId) {
		boolean imported = false;
		if (conflicts.size() > 0) {
			for (FacebookUser u : conflicts.get(user)) {
				if (u.getId().equals(facebookId)) {
					// Creates new or updates alumni user linkedin profile
					if (user.getProfile().getFacebookProfile() == null) {
						doImport(user, u);
						imported = true;
					} else {
						doUpdate(user, u);
						imported = true;
					}
				} 
				if (imported) {
					conflictedProfiles.remove(conflictedProfiles.indexOf(user.getProfile()));
					conflicts.remove(user);
					break;
				}
			}
		}
		return imported;
	}
	
	private void updateMatch(ApiClientBuilder builder, User user, String facebookId) {
		// TODO check if return is correct and perform update
		FacebookUser  fbuser = builder.getFacebookClient().fetchObject(facebookId, FacebookUser.class);
		FacebookProfile current_fb_user = user.getProfile().getFacebookProfile();
		// updates the user if last updated field is newer than current, updating as well th profile pic
		if (fbuser.getUpdatedTime().compareTo(current_fb_user.getLastUpdated()) > 0) {
			JsonObject jsonPicture = builder.getFacebookClient().fetchObject(fbuser.getId(), JsonObject.class, Parameter.with("fields", "picture"));
			fbuser.setPic(jsonPicture.getJsonObject("picture").getJsonObject("data").getString("url"));
			doUpdate(user, fbuser);
		}
	}
	
	private TreeMap <Integer, List<FacebookUser>> matchSingle(ApiClientBuilder client, User user) {
		List<FacebookUser> candidates = new ArrayList<FacebookUser>();
		int pagesLimit = 1;
		int i = 0;
		// TODO: aca deberia haber un try/catch que si no acepta los
		// tokens, se los pida de nuevo. MUY IMPORTANTE! FB LOS EXPIRA CADA 2 MESES SI O SI
		// (esta implementado en connectLinkedin, pero al revez la idea)
		Connection<FacebookUser> publicSearch =
				  client.getFacebookClient().fetchConnection("search", FacebookUser.class,
				    Parameter.with("q", user.getFullName()), Parameter.with("type", "user"),
				    Parameter.with("fields", "id, name, first_name, middle_name, last_name, gender," +
				    						"locale, languages, link, username,	age_range, third_party_id, installed," +
			    							"timezone, updated_time, verified, bio, birthday, currency,	education, email, hometown," +
			    							"interested_in, location, political, payment_pricepoints, favorite_athletes, favorite_teams, quotes," +
			    							"relationship_status, religion,	security_settings, significant_other, video_upload_limits, website," +
			    							"work"));
		//return processFacebookProfiles(user, people.getPersonList());
		// iterates over facebook pagination up to i pages
		while (publicSearch.hasNext() && i++ < pagesLimit) {
			candidates.addAll(publicSearch.getData());
			publicSearch = client.getFacebookClient().fetchConnectionPage(publicSearch.getNextPageUrl(), FacebookUser.class);
		}
		/*while(results.hasNext()) {
			FacebookUser fbuser = results.next();
			if (fbuser.getHometown().get)
		}*/
		return processFacebookProfiles(user, candidates, client);
	}
	
	private TreeMap<Integer, List<FacebookUser>> processFacebookProfiles(User user, List<FacebookUser> people, ApiClientBuilder client) {
		//scorear por fecha de nacimiento??
		// la libreria no soporta buscar por fecha nacimiento... seria tactico
		TreeMap<Integer, List<FacebookUser>> analyzedFacebookProfiles = new TreeMap<Integer, List<FacebookUser>>();
		Locale l = new Locale("ES");
		Collator collator = Collator.getInstance(l);
		collator.setStrength(Collator.PRIMARY);
		Calendar cal = Calendar.getInstance();
		Calendar birthCalendar = Calendar.getInstance();
		Date facebookDateOfBirth = null;
		int score = 0;
		cal.setTime(user.getProfile().getBirthdate());
		if (people != null) {
			for (FacebookUser u : people) {
				if (u.getEducation() != null) {
					for (FacebookUser.Education e : u.getEducation()) {
						NamedFacebookType school = e.getSchool();
						if (school != null && school.getName() != null && (collator.compare(school.getName(), itbaName) == 0 || collator.compare(school.getName(), itbaName2) == 0)) {
							score++;
						}
					}
				}
				facebookDateOfBirth = u.getBirthdayAsDate();
				// if matches date of birth, score increases!
				if (facebookDateOfBirth != null) {
					birthCalendar.setTime(facebookDateOfBirth);
					if (cal.get(Calendar.DAY_OF_MONTH) == birthCalendar.get(Calendar.DAY_OF_MONTH) 
						&& cal.get(Calendar.MONTH) == birthCalendar.get(Calendar.MONTH) &&
						cal.get(Calendar.YEAR) == birthCalendar.get(Calendar.YEAR)) {
						score++;
					}
				}
				if (analyzedFacebookProfiles.get(score) == null) {
					analyzedFacebookProfiles.put(score, new ArrayList<FacebookUser>());
				}
				JsonObject jsonPicture = client.getFacebookClient().fetchObject(u.getId(), JsonObject.class, Parameter.with("fields", "picture"));
				u.setPic(jsonPicture.getJsonObject("picture").getJsonObject("data").getString("url"));
				analyzedFacebookProfiles.get(score).add(u);
				score = 0;
			}
		} 
		return analyzedFacebookProfiles;
	}
	
	private boolean hasConflicts(TreeMap<Integer, List<FacebookUser>> matchedProfiles) {
		return matchedProfiles.lastEntry().getValue().size() > 1;
	}
	
	private void importCandidate(TreeMap<Integer, List<FacebookUser>> ranking, User candidate, 
			List<Profile> matchedProfiles) {
		doImport(candidate, ranking.lastEntry().getValue().get(0));
		// add result to list of matched candidates for info purposes
		matchedProfiles.add(candidate.getProfile());
		return;
	}
	
	private void doImport(User candidate, FacebookUser u) {
		FacebookProfile profile = candidate.getProfile().getFacebookProfile();
		if (profile == null) {
			profile = FacebookProfile.buildFacebookProfile(u);
			candidate.getProfile().setFacebookProfile(profile);
		} else {
			// Update facebook profile fields, does not create new instance (which would create
			// a new record in the db). 
			profile.setImportedProfile(u);
		}
		profile.setBatchUpdated(true);
		
	}
	
	private void doUpdate(User candidate, FacebookUser u) {
		candidate.getProfile().getFacebookProfile().setImportedProfile(u);
		candidate.getProfile().getFacebookProfile().setBatchUpdated(true);
	}

}
